package cn.hebutrun.oulabot.common.config;

import cn.hebutrun.oulabot.common.Constants;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.io.Serializable;
import java.time.Duration;
import java.util.Date;
import java.util.Objects;

@EnableCaching
@Configuration
public class LettuceRedisConfig {

  @Bean
  public RedisTemplate<String, Serializable> redisTemplate(
      LettuceConnectionFactory connectionFactory) {
    RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>();
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    redisTemplate.setConnectionFactory(connectionFactory);
    redisTemplate.setHashKeySerializer(new StringRedisSerializer());

    // LocalDatetime序列化
    Jackson2JsonRedisSerializer<Object> j2jrs = new Jackson2JsonRedisSerializer<>(Object.class);
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    // 解决jackson2无法反序列化LocalDateTime的问题
    objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    objectMapper.registerModule(new JavaTimeModule());
    objectMapper.activateDefaultTyping(
        objectMapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL);
    j2jrs.setObjectMapper(objectMapper);
    // 序列化 value 时使用此序列化方法
    redisTemplate.setValueSerializer(j2jrs);
    redisTemplate.setHashValueSerializer(j2jrs);

    return redisTemplate;
  }

  @Bean
  public CacheManager cacheManager(RedisTemplate<String, Serializable> template) {
    // 基本配置
    RedisCacheConfiguration defaultCacheConfiguration =
        RedisCacheConfiguration.defaultCacheConfig()
            // 设置key为String
            .serializeKeysWith(
                RedisSerializationContext.SerializationPair.fromSerializer(
                    template.getStringSerializer()))
            // 设置value 为自动转Json的Object
            .serializeValuesWith(
                RedisSerializationContext.SerializationPair.fromSerializer(
                    template.getValueSerializer()))
            // 不缓存null
            .disableCachingNullValues();

    Date now = new Date();
    // 构造一个redis缓存管理器
    return RedisCacheManager.RedisCacheManagerBuilder
        // Redis 连接工厂
        .fromConnectionFactory(Objects.requireNonNull(template.getConnectionFactory()))
        // 缓存配置
        .cacheDefaults(defaultCacheConfiguration)
        .withCacheConfiguration(
            Constants.REDIS_CACHE_NAME,
            RedisCacheConfiguration.defaultCacheConfig()
                // 设置key为String
                .serializeKeysWith(
                    RedisSerializationContext.SerializationPair.fromSerializer(
                        template.getStringSerializer()))
                // 设置value 为自动转Json的Object
                .serializeValuesWith(
                    RedisSerializationContext.SerializationPair.fromSerializer(
                        template.getValueSerializer()))
                // 不缓存null
                .disableCachingNullValues()
                // 设置0点过期
                .entryTtl(
                    Duration.ofSeconds(
                        DateUtil.between(now, DateUtil.endOfDay(now), DateUnit.SECOND))))
        // 配置同步修改或删除 put/evict
        .transactionAware()
        .build();
  }
}
